/*
 * Copyright (c) 2024 Huawei Technologies Co., Ltd.
 * openFuyao is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package v1

import (
	"reflect"
	"testing"
	"time"

	"openfuyao.com/monitoring-service/pkg/client"
)

func TestTransformQueryOptionsResources(t *testing.T) {
	type args struct {
		reqParams reqParams
		lvl       client.Level
	}
	tests := []struct {
		name          string
		args          args
		wantQueryOpts queryOptions
		wantErr       bool
	}{
		{
			name: "Test with LevelCluster",
			args: args{
				reqParams: reqParams{
					time:         "2024-05-13 09:00:00",
					metricFilter: "cluster_nodes_count",
				},
				lvl: client.LevelCluster,
			},
			wantQueryOpts: queryOptions{
				start:        time.Time{},
				end:          time.Time{},
				time:         time.Date(2024, 5, 13, 9, 0, 0, 0, time.Local),
				step:         time.Duration(0 * time.Second),
				metricFilter: "cluster_nodes_count",
				namedMetrics: []string{
					"cluster_nodes_count",
					"cluster_nodes_not_ready_count",
					"cluster_pods_count",
					"cluster_cpu_usage",
					"cluster_cpu_usage_by_namespace",
					"cluster_cpu_total",
					"cluster_cpu_utilisation",
					"cluster_cpu_requests_total",
					"cluster_cpu_requests_utilisation",
					"cluster_cpu_limits_total",
					"cluster_cpu_limits_utilisation",
					"cluster_memory_usage",
					"cluster_memory_usage_without_cache_by_namespace",
					"cluster_memory_total",
					"cluster_memory_utilisation",
					"cluster_pod_count_by_namespace",
					"cluster_workload_count_by_namespace",
					"cluster_memory_requests_total_by_namespace",
					"cluster_memory_requests_total",
					"cluster_memory_requests_utilisation",
					"cluster_memory_requests_utilisation_by_namespace",
					"cluster_memory_limits_total_by_namespace",
					"cluster_memory_limits_total",
					"cluster_memory_limits_utilisation",
					"cluster_memory_limits_utilisation_by_namespace",
					"cluster_rate_of_received_packets_by_namespace",
					"cluster_rate_of_transmitted_packets_by_namespace",
					"cluster_rate_of_received_packets_dropped_by_namespace",
					"cluster_rate_of_transmitted_packets_dropped_by_namespace",
					"cluster_iops_reads_by_namespace",
					"cluster_iops_writes_by_namespace",
					"cluster_iops_reads_writes_by_namespace",
					"cluster_throughput_reads_by_namespace",
					"cluster_throughput_writes_by_namespace",
					"cluster_throughput_reads_writes_by_namespace",
					"cluster_disk_size_usage",
					"cluster_disk_size_total",
					"cluster_disk_size_utilisation",
					"cluster_disk_read_iops",
					"cluster_disk_write_iops",
					"cluster_disk_read_throughput",
					"cluster_disk_write_throughput",
					"cluster_disk_inode_usage",
					"cluster_disk_inode_total",
					"cluster_disk_inode_utilisation",
					"cluster_network_bytes_transmitted",
					"cluster_network_bytes_received",
				},
				option: client.WithClusterLevel(),
			},
			wantErr: false,
		},

		{
			name: "Test with LevelDeployment",
			args: args{
				reqParams: reqParams{
					time:           "2024-05-13 09:00:00",
					metricFilter:   ".*",
					resourceFilter: ".*",
					namespaceName:  "monitoring",
					deploymentName: "blackbox-exporter",
				},
				lvl: client.LevelDeployment,
			},
			wantQueryOpts: queryOptions{
				start:        time.Time{},
				end:          time.Time{},
				time:         time.Date(2024, 5, 13, 9, 0, 0, 0, time.Local),
				step:         time.Duration(0 * time.Second),
				metricFilter: ".*",
				namedMetrics: []string{
					"workload_cpu_usage",
					"workload_memory_usage",
					"workload_network_packets_received",
					"workload_network_packets_transmitted",
					"workload_network_packets_received_dropped",
					"workload_network_packets_transmitted_dropped",
				},
				option: client.WithDeploymentDetails(".*", "monitoring",
					"blackbox-exporter"),
			},
			wantErr: false,
		},

		{
			name: "Test with LevelStatefulset",
			args: args{
				reqParams: reqParams{
					time:            "2024-05-13 09:00:00",
					metricFilter:    ".*",
					resourceFilter:  ".*",
					namespaceName:   "monitoring",
					statefulsetName: "alertmanager-main",
				},
				lvl: client.LevelStatefulset,
			},
			wantQueryOpts: queryOptions{
				start:        time.Time{},
				end:          time.Time{},
				time:         time.Date(2024, 5, 13, 9, 0, 0, 0, time.Local),
				step:         time.Duration(0 * time.Second),
				metricFilter: ".*",
				namedMetrics: []string{
					"workload_cpu_usage",
					"workload_memory_usage",
					"workload_network_packets_received",
					"workload_network_packets_transmitted",
					"workload_network_packets_received_dropped",
					"workload_network_packets_transmitted_dropped",
				},
				option: client.WithStatefulsetDetails(".*", "monitoring",
					"alertmanager-main"),
			},
			wantErr: false,
		},

		{
			name: "Test with LevelDaemonset",
			args: args{
				reqParams: reqParams{
					time:           "2024-05-13 09:00:00",
					metricFilter:   ".*",
					resourceFilter: ".*",
					namespaceName:  "monitoring",
					daemonsetName:  "node-exporter",
				},
				lvl: client.LevelDaemonset,
			},
			wantQueryOpts: queryOptions{
				start:        time.Time{},
				end:          time.Time{},
				time:         time.Date(2024, 5, 13, 9, 0, 0, 0, time.Local),
				step:         time.Duration(0 * time.Second),
				metricFilter: ".*",
				namedMetrics: []string{
					"workload_cpu_usage",
					"workload_memory_usage",
					"workload_network_packets_received",
					"workload_network_packets_transmitted",
					"workload_network_packets_received_dropped",
					"workload_network_packets_transmitted_dropped",
				},
				option: client.WithDaemonsetDetails(".*", "monitoring",
					"node-exporter"),
			},
			wantErr: false,
		},

		{
			name: "Test with LevelPod",
			args: args{
				reqParams: reqParams{
					time:          "2024-05-13 09:00:00",
					metricFilter:  ".*",
					namespaceName: "monitoring",
					podName:       "alertmanager-main-0",
				},
				lvl: client.LevelPod,
			},
			wantQueryOpts: queryOptions{
				start:        time.Time{},
				end:          time.Time{},
				time:         time.Date(2024, 5, 13, 9, 0, 0, 0, time.Local),
				step:         time.Duration(0 * time.Second),
				metricFilter: ".*",
				namedMetrics: []string{
					"pod_cpu_usage",
					"pod_memory_usage",
					"pod_network_bytes_received",
					"pod_network_bytes_transmitted",
					"pod_network_packets_received",
					"pod_network_packets_transmitted",
					"pod_network_packets_received_dropped",
					"pod_network_packets_transmitted_dropped",
					"pod_iops_reads",
					"pod_iops_writes",
					"pod_iops_reads_writes",
					"pod_throughput_reads",
					"pod_throughput_writes",
					"pod_throughput_reads_writes",
				},
				option: client.WithPodDetails("monitoring", "alertmanager-main-0"),
			},
			wantErr: false,
		},

		{
			name: "Test with unsupported level",
			args: args{
				reqParams: reqParams{},
				lvl:       999,
			},
			wantQueryOpts: queryOptions{},
			wantErr:       true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotQueryOpts, err := transformQueryOptionsResources(tt.args.reqParams, tt.args.lvl)
			if (err != nil) != tt.wantErr {
				t.Errorf("transformQueryOptionsResources() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if err != nil {
				return
			}

			gotOpts := client.NewQueryOptions(gotQueryOpts.option)
			wantOpts := client.NewQueryOptions(tt.wantQueryOpts.option)
			if !reflect.DeepEqual(gotOpts, wantOpts) {
				t.Errorf("Option did not set correctly, got: %v, want: %v", gotOpts, wantOpts)
				return
			}

			gotQueryOpts.option = nil
			tt.wantQueryOpts.option = nil
			if !reflect.DeepEqual(gotQueryOpts, tt.wantQueryOpts) {
				t.Errorf("transformQueryOptionsResources() gotQueryOpts = %v, want %v", gotQueryOpts, tt.wantQueryOpts)
			}
		})
	}
}

func TestTransformQueryOptionsComponents(t *testing.T) {
	type args struct {
		reqParams reqParams
		lvl       client.Level
	}
	tests := []struct {
		name          string
		args          args
		wantQueryOpts queryOptions
		wantErr       bool
	}{
		{
			name: "Test with LevelControlPlaneComponents - ETCD",
			args: args{
				reqParams: reqParams{
					time:         "2024-05-13 09:00:00",
					metricFilter: ".*",
					instance:     "192.168.100.5:2379",
					component:    client.ETCD,
				},
				lvl: client.LevelControlPlaneComponents,
			},
			wantQueryOpts: queryOptions{
				start:        time.Time{},
				end:          time.Time{},
				time:         time.Date(2024, 5, 13, 9, 0, 0, 0, time.Local),
				step:         time.Duration(0 * time.Second),
				metricFilter: ".*",
				namedMetrics: []string{
					"etcd_server_has_leader",
					"etcd_server_is_leader",
					"etcd_up_instances_count",
					"etcd_down_instances_count",
					"etcd_instances_total",
					"etcd_server_leader_changes_seen_1day",
					"etcd_backend_db_total",
					"etcd_backend_db_usage",
					"etcd_backend_db_commit_latency",
					"etcd_debugging_mvcc_keys_total",
					"etcd_kube_api_request_latency",
					"etcd_cpu_usage",
					"etcd_memory_usage",
					"etcd_goroutines",
				},
				option: client.WithControlPlaneComponentsETCD("etcd", "192.168.100.5:2379"),
			},
			wantErr: false,
		},

		{
			name: "Test with LevelNodeComponents - kubelet",
			args: args{
				reqParams: reqParams{
					time:         "2024-05-13 09:00:00",
					metricFilter: ".*",
					instance:     "192.168.100.5:10250",
					component:    client.Kubelet,
				},
				lvl: client.LevelNodeComponents,
			},
			wantQueryOpts: queryOptions{
				start:        time.Time{},
				end:          time.Time{},
				time:         time.Date(2024, 5, 13, 9, 0, 0, 0, time.Local),
				step:         time.Duration(0 * time.Second),
				metricFilter: ".*",
				namedMetrics: []string{
					"kubelet_running_count",
					"kubelet_running_pods_count",
					"kubelet_running_containers_count",
					"kubelet_managed_actual_volumes_count",
					"kubelet_managed_desired_volumes_count",
					"kubelet_runtime_operation_rate",
					"kubelet_runtime_operation_error_rate",
					"kubelet_pod_start_rate",
					"kubelet_pod_worker_rate",
					"kubelet_storage_operation_rate",
					"kubelet_storage_operation_error_rate",
					"kubelet_cgroup_manager_operation_rate",
					"kubelet_pleg_relist_rate",
					"kubelet_pleg_relist_interval",
					"kubelet_pleg_relist_latency",
					"kubelet_kube_api_2XX_request_rate",
					"kubelet_kube_api_3XX_request_rate",
					"kubelet_kube_api_4XX_request_rate",
					"kubelet_kube_api_5XX_request_rate",
					"kubelet_kube_api_request_latency",
					"kubelet_cpu_usage",
					"kubelet_memory_usage",
					"kubelet_goroutines",
				},
				option: client.WithNodeComponentKubelet("kubelet", "192.168.100.5:10250"),
			},
			wantErr: false,
		},

		{
			name: "Test with LevelAddonsComponents - coredns",
			args: args{
				reqParams: reqParams{
					time:         "2024-05-13 09:00:00",
					metricFilter: ".*",
					instance:     "172.17.177.104:9153",
					component:    client.CoreDNS,
				},
				lvl: client.LevelAddonsComponents,
			},
			wantQueryOpts: queryOptions{
				start:        time.Time{},
				end:          time.Time{},
				time:         time.Date(2024, 5, 13, 9, 0, 0, 0, time.Local),
				step:         time.Duration(0 * time.Second),
				metricFilter: ".*",
				namedMetrics: []string{
					"coredns_requests_rate",
					"coredns_requests_latency",
					"coredns_udp_responses_size_bytes",
					"coredns_tcp_responses_size_bytes",
					"coredns_NXDOMAIN_response_rate",
					"coredns_panic_count",
					"coredns_cache_size",
					"coredns_cache_hitrate",
				},
				option: client.WithAddonsComponentCoredns("coredns", "172.17.177.104:9153"),
			},
			wantErr: false,
		},

		{
			name: "Test with unsupported level",
			args: args{
				reqParams: reqParams{},
				lvl:       999,
			},
			wantQueryOpts: queryOptions{},
			wantErr:       true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotQueryOpts, err := transformQueryOptionsComponents(tt.args.reqParams, tt.args.lvl)
			if (err != nil) != tt.wantErr {
				t.Errorf("transformQueryOptionsResources() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if err != nil {
				return
			}

			gotOpts := client.NewQueryOptions(gotQueryOpts.option)
			wantOpts := client.NewQueryOptions(tt.wantQueryOpts.option)
			if !reflect.DeepEqual(gotOpts, wantOpts) {
				t.Errorf("Option did not set correctly, got: %v, want: %v", gotOpts, wantOpts)
				return
			}

			gotQueryOpts.option = nil
			tt.wantQueryOpts.option = nil
			if !reflect.DeepEqual(gotQueryOpts, tt.wantQueryOpts) {
				t.Errorf("transformQueryOptionsResources() gotQueryOpts = %v, want %v", gotQueryOpts, tt.wantQueryOpts)
			}
		})
	}
}

func TestTransformVerbResourceInstanceQueryOptions(t *testing.T) {
	type args struct {
		reqParams reqParams
		queryOpts queryOptions
	}
	tests := []struct {
		name    string
		args    args
		want    queryOptions
		wantErr bool
	}{
		{
			name: "KubeAPIServer with missing parameters (verb and resource)",
			args: args{
				reqParams: reqParams{component: "kube-apiserver", verb: "", resource: ""},
				queryOpts: queryOptions{},
			},
			want:    queryOptions{},
			wantErr: true,
		},

		{
			name: "KubeAPIServer with missing parameter (resource)",
			args: args{
				reqParams: reqParams{component: "kube-apiserver", verb: "LIST", resource: ""},
				queryOpts: queryOptions{},
			},
			want:    queryOptions{},
			wantErr: true,
		},

		{
			name: "KubeAPIServer with all parameters set",
			args: args{
				reqParams: reqParams{component: "kube-apiserver", verb: "LIST", resource: "apiservices",
					instance: "192.168.100.5:6443"},
				queryOpts: queryOptions{},
			},
			want:    queryOptions{},
			wantErr: true,
		},

		{
			name: "Valid case for KubeAPIServer",
			args: args{
				reqParams: reqParams{component: "kube-apiserver", verb: "LIST", resource: "apiservices",
					instance: ""},
				queryOpts: queryOptions{},
			},
			want:    queryOptions{},
			wantErr: false,
		},

		{
			name: "Non-KubeAPIServer with verb set",
			args: args{
				reqParams: reqParams{component: "kube-scheduler", verb: "GET", resource: "",
					instance: "192.168.100.5:10259"},
				queryOpts: queryOptions{},
			},
			want:    queryOptions{},
			wantErr: true,
		},

		{
			name: "Non-KubeAPIServer with missing instance",
			args: args{
				reqParams: reqParams{component: "kube-scheduler", verb: "", resource: "",
					instance: ""},
				queryOpts: queryOptions{},
			},
			want:    queryOptions{},
			wantErr: true,
		},

		{
			name: "Valid case for Non-KubeAPIServer",
			args: args{
				reqParams: reqParams{component: "kube-scheduler", verb: "", resource: "",
					instance: "192.168.100.5:10259"},
				queryOpts: queryOptions{},
			},
			want:    queryOptions{},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := transformVerbResourceInstanceQueryOptions(tt.args.reqParams, tt.args.queryOpts)
			if (err != nil) != tt.wantErr {
				t.Errorf("transformVerbResourceInstanceQueryOptions() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("transformVerbResourceInstanceQueryOptions() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestTransformTimeBasedQueryOptions(t *testing.T) {
	type args struct {
		reqParams reqParams
		queryOpts queryOptions
	}
	tests := []struct {
		name    string
		args    args
		want    queryOptions
		wantErr bool
	}{
		{
			name: "Valid instant time",
			args: args{
				reqParams: reqParams{
					time: "2024-05-04 10:40:00",
				},
				queryOpts: queryOptions{},
			},
			want: queryOptions{
				time: time.Date(2024, 5, 4, 10, 40, 0, 0, time.Local),
			},
			wantErr: false,
		},

		{
			name: "Valid instant time with unnecessary step",
			args: args{
				reqParams: reqParams{
					time: "2024-05-04 10:40:00",
					step: "15s",
				},
				queryOpts: queryOptions{},
			},
			want: queryOptions{
				time: time.Date(2024, 5, 4, 10, 40, 0, 0, time.Local),
			},
			wantErr: true,
		},

		{
			name: "Valid time range",
			args: args{
				reqParams: reqParams{
					start: "2024-05-04 10:40:00",
					end:   "2024-05-04 10:45:00",
				},
				queryOpts: queryOptions{},
			},
			want: queryOptions{
				start: time.Date(2024, 5, 4, 10, 40, 0, 0, time.Local),
				end:   time.Date(2024, 5, 4, 10, 45, 0, 0, time.Local),
				step:  time.Duration(15 * time.Second),
			},
			wantErr: false,
		},

		{
			name: "Invalid time range (invalid format for start time)",
			args: args{
				reqParams: reqParams{
					start: "2024-05-4 10:40:00",
					end:   "2024-05-04 10:45:00",
				},
				queryOpts: queryOptions{},
			},
			want:    queryOptions{},
			wantErr: true,
		},

		{
			name: "Invalid time range (start time equals end time)",
			args: args{
				reqParams: reqParams{
					start: "2024-05-04 10:45:00",
					end:   "2024-05-04 10:45:00",
				},
				queryOpts: queryOptions{},
			},
			want: queryOptions{
				start: time.Date(2024, 5, 4, 10, 45, 0, 0, time.Local),
				end:   time.Date(2024, 5, 4, 10, 45, 0, 0, time.Local),
			},
			wantErr: true,
		},

		{
			name: "Invalid time range (start time after end time)",
			args: args{
				reqParams: reqParams{
					start: "2024-05-04 10:46:00",
					end:   "2024-05-04 10:45:00",
				},
				queryOpts: queryOptions{},
			},
			want: queryOptions{
				start: time.Date(2024, 5, 4, 10, 46, 0, 0, time.Local),
				end:   time.Date(2024, 5, 4, 10, 45, 0, 0, time.Local),
			},
			wantErr: true,
		},

		{
			name: "Invalid time range (invalid format for step)",
			args: args{
				reqParams: reqParams{
					start: "2024-05-04 10:46:00",
					end:   "2024-05-04 10:45:00",
					step:  "5",
				},
				queryOpts: queryOptions{},
			},
			want: queryOptions{
				start: time.Date(2024, 5, 4, 10, 46, 0, 0, time.Local),
				end:   time.Date(2024, 5, 4, 10, 45, 0, 0, time.Local),
			},
			wantErr: true,
		},

		{
			name: "Invalid time range (missing start time or end time)",
			args: args{
				reqParams: reqParams{
					start: "2024-05-04 10:46:00",
				},
				queryOpts: queryOptions{},
			},
			want:    queryOptions{},
			wantErr: true,
		},

		{
			name: "Time conflict error",
			args: args{
				reqParams: reqParams{
					start: "2024-05-04 10:40:00",
					end:   "2024-05-04 10:45:00",
					time:  "2024-05-04 10:45:00",
				},
				queryOpts: queryOptions{},
			},
			want:    queryOptions{},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := transformTimeBasedQueryOptions(tt.args.reqParams, tt.args.queryOpts)
			if (err != nil) != tt.wantErr {
				t.Errorf("transformTimeBasedQueryOptions() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("transformTimeBasedQueryOptions() got = %v, want %v", got, tt.want)
			}
		})
	}
}
